﻿/* mvcForum
 * Copyright (C) 2010-2011 Steen F. Tøttrup
 * http://mvcforum.org/
 */

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using mvcForum.Core;
using mvcForum.Core.Abstractions.Interfaces;
using mvcForum.Core.Interfaces.Data;
using mvcForum.Core.Specifications;

namespace mvcForum.DataProvider.EntityFramework.Repositories {

	public class TopicRepository : Repository<Topic>, ITopicRepository {
		private readonly IConfiguration config;

		public TopicRepository(DbContext context, IConfiguration config)
			: base(context) {

			this.config = config;
		}

		public IList<Topic> ReadTopics(Forum forum, Int32 page, ForumUser user, Boolean isModerator) {
			// Let's get all the announcements, they'll show up first on all pages!
			IEnumerable<Topic> announcements = this.ReadAnnouncements(forum, user, isModerator);
			// Do the number of announcements allow any other topics to be shown?
			if (announcements.Count() < this.config.TopicsPerPage) {
				// Get the topics of the forum, and only those we'll need to show!
				announcements = announcements.Concat(this.ReadStickiesAndRegulars(forum, page, announcements.Count(), user, isModerator));
			}
			return announcements.ToList();
		}

		public IList<Topic> ReadStickiesAndRegulars(Forum forum, Int32 page, Int32 announcementCount, ForumUser user, Boolean isModerator) {
			// TODO: How will this work when we have thousands and thousands of posts!??!?!
			// Use the LastPosted and Posted properties so we get the latest at the top!
			// Get stickies first, then the regular posts!
			IEnumerable<Topic> tops;
			tops = this
						.ReadManyOptimized(new TopicSpecifications.StickiesAndRegularsNotSpam(forum, this.config.SpamAverage, user, isModerator))
						.OrderBy(t => t.Type)
						.ThenByDescending(t => t.LastPosted);
			//.ThenByDescending(t => t.Posted);

			// TODO: Is this correct?? What happens if announcementCount > config.TopicsPerPage ?!?!?!??!
			return tops
					.Skip<Topic>((page - 1) * (this.config.TopicsPerPage - announcementCount))
					.Take(config.TopicsPerPage - announcementCount).ToList();
		}

		/// <summary>
		/// Read all quarantined topics in the given forum.
		/// </summary>
		/// <param name="forum">The forum.</param>
		/// <returns>A list of quarantined topics.</returns>
		public IList<Topic> ReadQuarantined(Forum forum) {
			return this.ReadAllOptimized()
						.Where(t => t.ForumId == forum.Id).AsQueryable()
						.Where(new TopicSpecifications.Visible().IsSatisfied)
						.OrderByDescending(t => t.Posted)
						.ToList();
		}

		public IList<Topic> ReadAnnouncements(Forum forum, ForumUser user, Boolean isModerator) {
			// TODO: How will this work when we have thousands and thousands of posts!??!?!
			// TODO: Other access types !?!?!?!?
			return this.ReadManyOptimized(new TopicSpecifications.AnnouncementsNotSpam(forum, this.config.SpamAverage, user, isModerator))
							.OrderByDescending(t => t.LastPosted)
							.ThenByDescending(x => x.Posted)
							.ToList();
		}

		/// <summary>
		/// Read the topics that match the specification, including latest post and author.
		/// </summary>
		/// <param name="spec">The specification the topics must match.</param>
		/// <returns>A list of matching topics.</returns>
		public IEnumerable<Topic> ReadManyOptimized(ISpecification<Topic> spec) {
			return this.ReadOptimized(spec);
		}

		private IQueryable<Topic> ReadOptimized(ISpecification<Topic> spec) {
			return this.set
						.Include(t => t.Forum)
						.Include(t => t.LastPost)
						.Include(t => t.LastPostAuthor)
						.Include(t => t.Author)
						.Where(spec.IsSatisfied);
		}

		/// <summary>
		/// Read one topic that match the specification, including latest post and author.
		/// </summary>
		/// <param name="spec">The specification the topic must match.</param>
		/// <returns>A matching topic or null.</returns>
		public Topic ReadOneOptimized(ISpecification<Topic> spec) {
			return this.ReadOptimized(spec).FirstOrDefault();
		}

		public Topic ReadOneOptimizedWithPosts(Int32 id) {
			return this.ReadOptimized(new TopicSpecifications.ById(id)).Include(t => t.Posts).FirstOrDefault();
		}

		public IEnumerable<Topic> ReadAllOptimized() {
			return this.set
						.Include(t => t.Forum)
						.Include(t => t.LastPost)
						.Include(t => t.LastPostAuthor)
						.Include(t => t.Author);
		}

		public Topic ReadOneOptimizedWithPosts(ISpecification<Topic> spec) {
			return this.ReadOptimized(spec).Include(t => t.Posts).FirstOrDefault();
		}
	}
}